/**
 * @author Zvonimir Fras <zvonimir@frasbros.com>
 */

#include "./rumainwindow.h"

#include <QFileDialog>
#include <QMessageBox>
#include <QTimer>

#include "util.h"
#include "opencv/cv.h"


RUMainWindow::RUMainWindow(QWidget* parent) : QMainWindow( parent )
{
   setupUi( this );
   
   connect( openTrainingSetButton, SIGNAL(clicked(bool)), action_Open_Training_Set, SIGNAL(triggered(bool)) );
   connect( &m_runner, SIGNAL(finished()), this, SLOT(slotOptimizingFinnished()) );
   
   connect( &m_runner, SIGNAL(iterationCompleted(int)), progressBar, SLOT(setValue(int)) );
   connect( &m_runner, SIGNAL(sampleCompleted(int)), progressBar2, SLOT(setValue(int)) );
   connect( &m_runner, SIGNAL(newBestFitnessFound(double)), currentlyBestFitnessLcdNumber, SLOT(display(double)));
   connect( &m_runner, SIGNAL(iterationCompleted(int)), currentIterationLcdNumber, SLOT(display(int)) );
   connect( &m_runner, SIGNAL(sampleCompleted(int)), currentSampleLcdNumber, SLOT(display(int)) );
}



void RUMainWindow::on_action_Open_Training_Set_triggered()
{
   QString trainingSetDirString = QFileDialog::getExistingDirectory( this, tr("Open Training Set") );
   
   if( trainingSetDirString.isEmpty() ) return;
   
   slotOpenTrainingSet( trainingSetDirString );
}



void RUMainWindow::on_action_Open_Network_triggered()
{
   QString filename = QFileDialog::getOpenFileName( this, tr("Open Network") );
   
   if( filename.isEmpty() ) return;
   
   m_ann.load( filename );
}


void RUMainWindow::on_action_Save_Network_triggered()
{
   QString filename = QFileDialog::getSaveFileName( this, tr("Save Network") );
   
   if( filename.isEmpty() ) return;
   
   m_ann.save( filename );
}


void RUMainWindow::on_image1Button_clicked()
{
   QString filename = QFileDialog::getOpenFileName( this, tr("Open First Image") );
   if( filename.isEmpty() ) return;
   
   image1Button->setIcon( QIcon( filename ) );
}


void RUMainWindow::on_image2Button_clicked()
{
   QString filename = QFileDialog::getOpenFileName( this, tr("Open Second Image") );
   if( filename.isEmpty() ) return;
   
   image2Button->setIcon( QIcon( filename ) );
}


void RUMainWindow::on_compareButton_clicked()
{
   QList< float > input = m_imagesToNeuralNetworkInput( image1Button->icon().pixmap( image1Button->iconSize() ).toImage(),
							image2Button->icon().pixmap( image2Button->iconSize() ).toImage() );
   QList< float > output = m_ann.output( input );
   
   if( output.size() < 2 )
   {
      resultLabel->setText( tr("<font color=red>Neural network error!</font><br>Expected output size = 2") );
      return;
   }
   
   
   if( output.at( 0 ) > 0.5 && output.at( 1 ) < 0.5 )
      resultLabel->setText( tr("Both palm pictures belong<br>to the <strong>same</strong> person.") );
   
   else if( output.at( 0 ) < 0.5 && output.at( 1 ) > 0.5 )
      resultLabel->setText( tr("Palm pictures belong to<br><strong>different</strong> persons.") );
   
   else
      resultLabel->setText( tr("Neural network is undecidable<br>%1 % Same<br>%2 % Different")
		     .arg( output.at(0) * 100 ).arg( output.at(1) * 100 ) );
}



void RUMainWindow::on_openTestSetFolderButton_clicked()
{
   QString folderName = QFileDialog::getExistingDirectory( this, tr("Open Test Set Folder") );
   if( folderName.isEmpty() ) return;
   
   testSetFolderLineEdit->setText( folderName );
   
   // TODO further processing if needed or testSetFolderLineEdit->text() can be used elsewhere to access folder name
}



void RUMainWindow::on_createButton_clicked()
{
   QStringList params = networkStructureLineEdit->text().split( QRegExp("\\s+"), QString::SkipEmptyParts );
   QList< int > networkStructure;
   
   networkStructure.append( ANN_INPUT_SIZE );
   
   foreach( QString param, params )
   {
      bool ok = false;
      int p = param.toInt( &ok );
      if( !ok )
      {
	 // report error
	 QMessageBox::warning( this, tr("Warning!"), tr("All parameters have to be int!") );
	 return;
      }
      networkStructure.append( p );
   }
   
   networkStructure.append( ANN_OUTPUT_SIZE );
   
   
   m_networkStructure = networkStructure;
   m_ann.random( m_networkStructure );
   
   // log
   log( tr("Created network with following structure:") );
   QString structure;
   foreach( int n, m_ann.networkStructure() )
   {
      structure.append( tr("%1 ").arg(n) );
   }
   log( structure );
}




void RUMainWindow::on_trainButton_clicked()
{
   if( m_runner.isRunning() )
   {
      m_runner.stopOptimizing();
      QTimer::singleShot( 0, &m_runner, SLOT(terminate()) );
      m_runner.wait();
      return;
   }
   
   if( algorithmComboBox->currentIndex() == 2 ) // backpropagation
   {
      // HACK HACK HACK this is a hack, backpropagation should have a separate runner or runner should be named differently
      
      m_pso.setAlgorithm( algorithmComboBox->currentIndex() );
      m_pso.setCutoff( cutoffSpinBox->value() );
      m_pso.setMaxIterations( maxIterSpinBox->value() );
      m_pso.clearSwarm();
      m_pso.addToSwarm( m_ann );
      m_runner.setPso( m_pso );
      progressBar->setEnabled( true );
      progressBar->setMaximum( maxIterSpinBox->value() );
      progressBar2->setEnabled( true );
      progressBar2->setMaximum( m_pso.trainingSetSize() );
      trainButton->setText( tr("&Stop") );
      m_runner.start();
      
      log( tr("<b>Training started</b>\n") );
      QString structure;
      foreach( int size, m_ann.networkStructure() )
      {
	 structure.append( tr("%1 ").arg(size) );
      }
      log( tr("Network structure: %1").arg( structure ) );
      return;
   }
   
   
   
   
   m_pso.setMaxV( maxVSpinBox->value() );
   m_pso.setMaxX( maxXSpinBox->value() );
   m_pso.setMaxIterations( maxIterSpinBox->value() );
   m_pso.setCutoff( cutoffSpinBox->value() );
   m_pso.setWeight( inertiaWeightSpinBox->value() );
   m_pso.setInertiaWeightType( inertiaWeightTypeComboBox->currentIndex() );
   
   m_pso.setAlgorithm( algorithmComboBox->currentIndex() );
   
   m_pso.clearSwarm();
   // TODO adding networks to swarm in background (freezes gui on large nets and/or swarms)
   for( int i = 0;i<swarmSizeSpinBox->value();i++ )
   {
      NeuralNetwork network;
      network.random( m_networkStructure );
      m_pso.addToSwarm( network );
   }
   
   m_runner.setPso( m_pso );
   progressBar->setEnabled( true );
   progressBar->setMaximum( maxIterSpinBox->value() );
   progressBar2->setEnabled( true );
   progressBar2->setMaximum( m_pso.trainingSetSize() );
   trainButton->setText( tr("&Stop") );
   m_runner.start();
//    m_pso.optimize();
   
   
   // log
   log( tr("<b>Training started</b>\n") );
   QString structure;
   foreach( int size, m_ann.networkStructure() )
   {
      structure.append( tr("%1 ").arg(size) );
   }
   log( tr("Network structure: %1").arg( structure ) );
   log( tr("Max V:\t %1").arg( maxVSpinBox->value() ) );
   log( tr("Max Iter:\t %1").arg( maxIterSpinBox->value() ) );
   log( tr("cutoff:\t %1").arg( cutoffSpinBox->value() ) );
   log( tr("weight:\t %1").arg( inertiaWeightSpinBox->value() ) );
   if( inertiaWeightTypeComboBox->currentIndex() == 0 )
      log( tr("Weight Type:\t CONSTANT") );
   else log( tr("Weight Type:\t TIME VARIANT") );
}





void RUMainWindow::on_testButton_clicked()
{
    QDir imgDir(testSetFolderLineEdit->text());
    QStringList nameFilter;
    nameFilter.append("*.bmp");
    imgDir.setNameFilters(nameFilter);
    QStringList imageNames = imgDir.entryList();
    int correct=0;
    int incorrect=0;
    int undecided=0;
    foreach(QString img, imageNames)
    {
        QImage image1(testSetFolderLineEdit->text()+"/"+img);
        imageNames.removeOne(img);
        foreach(QString img2,imageNames)
        {
            QImage image2(testSetFolderLineEdit->text()+"/"+img2);
            //logTest("Opening:"+img+" ,"+img2);
            QList <float> inputs=m_imagesToNeuralNetworkInput(image1,image2);
            QList <float> outputs=m_ann.output(inputs);
            if( outputs.at( 0 ) > 0.5 && outputs.at( 1 ) < 0.5 )
            {
                if(!QString::compare(img.left(3),img2.left(3)) )
                    correct++;
                else
                    incorrect++;
            }

         else if( outputs.at( 0 ) < 0.5 && outputs.at( 1 ) > 0.5 )
             if(QString::compare(img.left(3),img2.left(3)) )
                 correct++;
             else
                 incorrect++;

         else
            undecided++;


        }
    }
    QString cor,incor,undec;
    cor.setNum(correct);
    incor.setNum(incorrect);
    undec.setNum(undecided);
    QString accuracy;
    accuracy.setNum(100 * (float)correct / (correct + incorrect));
    logTest("Correct: "+cor
            +" incorrect: "+incor
            +" undecided: "+undec
            +" accuracy: " + accuracy + "%");
}




void RUMainWindow::slotOptimizingFinnished()
{
   m_pso = m_runner.pso();
   trainButton->setText( tr("&Train") );
   progressBar->setValue( 24 );
   progressBar->setEnabled( false );
   progressBar2->setValue( 24 );
   progressBar2->setEnabled( false );
   m_ann = m_pso.bestNetwork();
   log( tr("<b>Training finnished</b>") );
}




void RUMainWindow::log(const QString& log)
{
   logBrowser->append( log );
}


void RUMainWindow::logTest(const QString& log)
{
   testLogBrowser->append( log );
}


void RUMainWindow::slotOpenTrainingSet(const QString& trainingSetPath)
{
   QDir dir( trainingSetPath );

   m_pso.clearTrainingSet();
   QStringList filters;
   filters.append("*.bmp");
   dir.setNameFilters(filters);
   QStringList files = dir.entryList(QDir::Files);
   foreach( QString file, files )
   {
      QImage image( dir.absolutePath()+"/"+file );

      files.removeOne(file);
      foreach(QString file2,files)
      {
        QList<float> trainingInput;
        QImage image2(dir.absolutePath()+"/"+file2);

        trainingInput=m_imagesToNeuralNetworkInput(image,image2);
        QList<float> trainingOutput;
        if(!QString::compare(file.left(3),file2.left(3)))
        {
          trainingOutput.append(1.0);
          trainingOutput.append(0.0);

        }
        else
        {
          trainingOutput.append(0.0);
          trainingOutput.append(1.0);
        }
        m_pso.addTrainingSet(trainingInput,trainingOutput);
  }


   }

}





QString RUMainWindow::m_readNextLine(QFile& file)
{
   QString empty;
   if( !file.isOpen() ) return empty;
   
   while( !file.atEnd() )
   {
      QString line = QString::fromUtf8( file.readLine().data() );
      if( line.isEmpty() ) continue;
      
      // strip leading blanks (spaces and tabs)
      int breakPoint = 0;
      foreach( QChar c, line ) if( c == ' ' || c == '\t' || c == '\n' ) breakPoint++; else break; // finds first non-blank
      line = line.right( line.size() - breakPoint );
      
      // strip comments
      breakPoint = 0;
      foreach( QChar c, line ) if( c != '#' ) breakPoint++; else break; // finds comment start
      line = line.left( breakPoint );
      
      // strip ending blanks (spaces and tabs)
      while( line.endsWith( " " ) || line.endsWith( "\t" ) || line.endsWith( "\n" ) ) line.remove( line.size()-1, 1 );
      
      
      if( line.isEmpty() ) continue;
      else return line;
   }
   
   return empty;
}


QList< float > RUMainWindow::m_imagesToNeuralNetworkInput(const QImage& image1, const QImage& image2)
{
   QList< float > retVal = m_imageFeatures( image1 );
   retVal.append( m_imageFeatures( image2 ) );
   return retVal;
}

QList< float > RUMainWindow::m_imageFeatures(const QImage& image)
{
   QList< float > retVal;
   
   cv::Mat mat = Util::QImage2Mat(image);
   cv::Mat edges;
   cvtColor(mat, edges, CV_BGR2GRAY);

   //image preprocessing
   cv::GaussianBlur(edges, edges, cv::Size(7,7), 1.5, 1.5);
   //edge detection
   cv::Canny(edges, edges, 0, 30, 3);

   // TODO extract features - PCA
   // temporary solution

   IplImage img = edges;
   int count;

   // counts white pixels in rows
   for (int i = 0; i < IMAGE_WIDTH; i++)
   {
       count = 0;
       for (int j = 0; j < IMAGE_WIDTH; j++){
           int pixelValue = (int)img.imageData[i*IMAGE_WIDTH + j];
           if (pixelValue != 0)
               count++;
       }
       retVal.append((float)count / IMAGE_WIDTH);
   }

   // counts white pixels in columns
   for (int i = 0; i < IMAGE_WIDTH; i++)
   {
       count = 0;
       for (int j = 0; j < IMAGE_WIDTH; j++){
           int pixelValue = (int)img.imageData[i + j*IMAGE_WIDTH];
           if (pixelValue != 0)
               count++;
       }
       retVal.append((float)count / IMAGE_WIDTH);
   }

   return retVal;
}
